/*
 * 小数计算，内部使用bignumber.js，原方法计算乘法0.49999750001249993 * 51.500008 后格式化有问题
 * @example:
 * 17.85+1.05 //18.900000000000002
 * 0.1+0.2     //0.30000000000000004
 * 0.49999750001249993 * 51.500008 //25.749875250623745
 *
 * var a=Decimal('0.1');
 * var b=Decimal('0.2');
 * a.add(b).toNumber()    //0.3
 *
 * 四舍五入，保留一位小数
 * a.add(b).add(0.14).toNumber(1)  //0.4
 *
 * 链式调用
 * (0.1+0.2+0.3)*2/0.5      //2.4000000000000004
 * Decimal.add([0.1,0.2,0.3]).mul(2).div(0.5).toNumber() //2.4
 *
 * 批量参数：可传递多个参数或者参数数组
 * Decimal.add(0.1,0.2,0.3).toNumber() //0.6
 * Decimal.add([0.1,0.2,0.3]).toNumber() //0.6
 * Decimal.sub(100,10,10).toNumber() //80
 * Decimal.mul(10,10,10).toNumber() //1000
 * Decimal.div(10,5,2).toNumber() //1
 *
 * 批量参数：可传递多个参数或者参数数组，返回结果直接转数字
 * Decimal.addBatch(0.1,0.2,0.3) //0.6
 * Decimal.subBatch(100,10,10) //80
 * Decimal.mulBatch(10,10,10) //1000
 * Decimal.divBatch(10,5,2) //1
 *
 * */
(function (ROOT, factory) {
    if (typeof exports === 'object') {
        // Node.
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(factory);
    } else {
        // Browser globals (root is window)
        // ROOT.Decimal = factory();
    }
}((0, eval)(this), function () {
    var DECIMAL_SEPARATOR = '.';

    // Decimal
    var Decimal = function (num) {
        if (this.constructor != Decimal) {
            return new Decimal(num);
        }

        if (num instanceof Decimal) {
            return num;
        }

        this.internal = String(num);
        this.as_int = as_integer(this.internal);

        //加法
        this.add = function (target) {
            var a = this.toNumber();
            var b = new Decimal(target).toNumber();
            var result = new BigNumber(a).plus(b).toNumber();
            return Decimal(result);
            /*var operands = [this, new Decimal(target)];
            operands.sort(function (x, y) {
                return x.as_int.exp - y.as_int.exp
            });

            var smallest = operands[0].as_int.exp;
            var biggest = operands[1].as_int.exp;

            var x = Number(format(operands[1].as_int.value, biggest - smallest));
            var y = Number(operands[0].as_int.value);

            var result = String(x + y);

            return Decimal(format(result, smallest));*/
        };

        //减法
        this.sub = function (target) {
            var a = this.toNumber();
            var b = new Decimal(target).toNumber();
            var result = new BigNumber(a).minus(b).toNumber();
            return Decimal(result);
            // return Decimal(this.add(target * -1));
        };

        //乘法
        this.mul = function (target) {
            var a = this.toNumber();
            var b = new Decimal(target).toNumber();
            var result = new BigNumber(a).multipliedBy(b).toNumber();
            return Decimal(result);
            // target = new Decimal(target);
            // var result = String(this.as_int.value * target.as_int.value);
            // var exp = this.as_int.exp + target.as_int.exp;
            // console.log('乘法', this.as_int, target.as_int, result, exp, format(result, exp));
            // return Decimal(format(result, exp));
        };

        //除法
        this.div = function (target) {
            var a = this.toNumber();
            var b = new Decimal(target).toNumber();
            var result = new BigNumber(a).dividedBy(b).toNumber();
            return Decimal(result);

            // target = new Decimal(target);
            // var smallest = Math.min(this.as_int.exp, target.as_int.exp);
            // var x = Decimal.mul(Math.pow(10, Math.abs(smallest)), this);
            // var y = Decimal.mul(Math.pow(10, Math.abs(smallest)), target);
            // return Decimal(x / y);
        };

        this.toString = function (precision) {
            if (isNumber(precision)) {
                return '' + toFixed(Number(this.internal), precision);
            }
            return this.internal;
        };

        this.toNumber = function (precision) {
            if (isNumber(precision)) {
                return toFixed(Number(this.internal), precision);
            }
            return Number(this.internal);
        }

    };

    var as_integer = function (number) {
        number = String(number);

        var value,
            exp,
            tokens = number.split(DECIMAL_SEPARATOR),
            integer = tokens[0],
            fractional = tokens[1];

        if (!fractional) {
            var trailing_zeros = integer.match(/0+$/);

            if (trailing_zeros) {
                var length = trailing_zeros[0].length;
                value = integer.substr(0, integer.length - length);
                exp = length;
            } else {
                value = integer;
                exp = 0;
            }
        } else {
            value = parseInt(number.split(DECIMAL_SEPARATOR).join(''), 10);
            exp = fractional.length * -1;
        }

        return {
            'value': value,
            'exp': exp
        };
    };


    // Helpers
    var neg_exp = function (str, position) {
        position = Math.abs(position);

        var offset = position - str.length;
        var sep = DECIMAL_SEPARATOR;

        if (offset >= 0) {
            str = zero(offset) + str;
            sep = '0.';
        }

        var length = str.length;
        var head = str.substr(0, length - position);
        var tail = str.substring(length - position, length);
        return head + sep + tail;
    };

    var pos_exp = function (str, exp) {
        var zeros = zero(exp);
        return String(str + zeros);
    };

    var format = function (num, exp) {
        num = String(num);
        var func = exp >= 0 ? pos_exp : neg_exp;
        return func(num, exp);
    };
    Decimal.format = format;
    Decimal.neg_exp = neg_exp;
    Decimal.pos_exp = pos_exp;

    var zero = function (exp) {
        return new Array(exp + 1).join('0');
    };

    //加减乘除方法,如果有多个参数时，循环计算
    var methods = ['add', 'mul', 'sub', 'div'];
    for (var i = 0; i < methods.length; i++) {
        (function (method) {
            Decimal[method] = function () {
                var args = [].slice.call(arguments);
                if (isArray(args[0])) {
                    args = args[0];
                }
                //当某个参数为null时，替换该参数为0
                for (var j = 0; j < args.length; j++) {
                    if (!args[j]) {
                        args[j] = 0;
                    }
                }
                if (args.length == 1) {
                    return new Decimal(args[0]);
                }
                var option = args[args.length - 1];

                var sum = new Decimal(args[0]),
                    index = 1;
                while (index < args.length) {
                    sum = sum[method](args[index]);
                    index++;
                }
                return sum;
            };
        })(methods[i]);
    }
    //批量参数加减乘除方法，直接调用加减乘除后，转换为数字
    var methods = ['addBatch', 'mulBatch', 'subBatch', 'divBatch'];
    for (var i = 0; i < methods.length; i++) {
        (function (method) {
            Decimal[method] = function () {
                var decimalMethod = method.replace('Batch', '');
                var args = [].slice.call(arguments);
                if (isArray(args[0])) {
                    args = args[0];
                }
                var value = Decimal[decimalMethod](args);
                return value.toNumber();
            };
        })(methods[i]);
    }

    var toFixed = function (number, precision) {
        var multiplier = Math.pow(10, precision + 1),
            wholeNumber = Math.floor(number * multiplier);
        return Math.round(wholeNumber / 10) * 10 / multiplier;
    };
    var isNumber = function (o) {
        return Object.prototype.toString.call(o).slice(8, -1) === 'Number';
    };
    var isArray = function (o) {
        return Object.prototype.toString.call(o).slice(8, -1) === 'Array';
    };
    var isObject = function (o) {
        return Object.prototype.toString.call(o).slice(8, -1) === 'Object';
    };
    return Decimal;
}));